#include "types.h"
#include "globals.h"
#include "kernel.h"

#include "util/gdb.h"
#include "util/init.h"
#include "util/debug.h"
#include "util/string.h"
#include "util/printf.h"

#include "mm/mm.h"
#include "mm/page.h"
#include "mm/pagetable.h"
#include "mm/pframe.h"

#include "vm/vmmap.h"
#include "vm/shadow.h"
#include "vm/anon.h"

#include "main/acpi.h"
#include "main/apic.h"
#include "main/interrupt.h"
#include "main/cpuid.h"
#include "main/gdt.h"

#include "proc/sched.h"
#include "proc/proc.h"
#include "proc/kthread.h"

#include "drivers/dev.h"
#include "drivers/blockdev.h"
#include "drivers/tty/virtterm.h"

#include "api/exec.h"
#include "api/syscall.h"

#include "fs/vfs.h"
#include "fs/vnode.h"
#include "fs/vfs_syscall.h"
#include "fs/fcntl.h"
#include "fs/stat.h"

#include "test/kshell/kshell.h"

#include "errno.h"

/*** test switch ***/
/*#define TEST_SEGFAULT*/
/*#define TEST_ARGS*/
#define TEST_HELLO
/*#define TEST_KSHELL*/
/*#define TEST_SPIN*/



GDB_DEFINE_HOOK(boot)
GDB_DEFINE_HOOK(initialized)
GDB_DEFINE_HOOK(shutdown)

static void      *bootstrap(int arg1, void *arg2);
static void      *idleproc_run(int arg1, void *arg2);
static kthread_t *initproc_create(void);
static void      *initproc_run(int arg1, void *arg2);
static void       hard_shutdown(void);

static context_t bootstrap_context;

/**
 * This is the first real C function ever called. It performs a lot of
 * hardware-specific initialization, then creates a pseudo-context to
 * execute the bootstrap function in.
 */
void
kmain()
{
        GDB_CALL_HOOK(boot);	/* what does this sentence mean? */

        dbg_init();
        dbgq(DBG_CORE, "Kernel binary:\n");
        dbgq(DBG_CORE, "  text: 0x%p-0x%p\n", &kernel_start_text, &kernel_end_text);
        dbgq(DBG_CORE, "  data: 0x%p-0x%p\n", &kernel_start_data, &kernel_end_data);
        dbgq(DBG_CORE, "  bss:  0x%p-0x%p\n", &kernel_start_bss, &kernel_end_bss);

        page_init();	/* init pagegroup list and set page_freecount to zero */

        pt_init();
        slab_init();
        pframe_init();

        acpi_init();
        apic_init();
        intr_init();

        gdt_init();

        /* initialize slab allocators */
#ifdef __VM__
        anon_init();
        shadow_init();
#endif
        vmmap_init();
        proc_init();/* initial the global process list and slab_allocator */
        kthread_init();

#ifdef __DRIVERS__
        bytedev_init();
        blockdev_init();
#endif

        void *bstack = page_alloc();
        pagedir_t *bpdir = pt_get();
        KASSERT(NULL != bstack && "Ran out of memory while booting.");
        context_setup(&bootstrap_context, bootstrap, 0, NULL, bstack, PAGE_SIZE, bpdir);
        context_make_active(&bootstrap_context);

        panic("\nReturned to kmain()!!!\n");
}

/**
 * This function is called from kmain, however it is not running in a
 * thread context yet. It should create the idle process which will
 * start executing idleproc_run() in a real thread context.  To start
 * executing in the new process's context call context_make_active(),
 * passing in the appropriate context. This function should _NOT_
 * return.
 *
 * Note: Don't forget to set curproc and curthr appropriately.
 *
 * @param arg1 the first argument (unused)
 * @param arg2 the second argument (unused)
 */
static void *
bootstrap(int arg1, void *arg2)
{
        /* necessary to finalize page table information */
        pt_template_init();

        /*----Yuanliang Yu code below ----*/
	/* local varialbes */
	proc_t *pIdleProc;
	kthread_t *ktIdleProc;

	/* create idle proc */ 
        pIdleProc = proc_create("idleProc");        
        KASSERT( pIdleProc != NULL );

        /* create 1st thread in  idle proc . the body of thread is idleproc_run */
        ktIdleProc = kthread_create( pIdleProc,idleproc_run, NULL, NULL );
        KASSERT( ktIdleProc != NULL );
        
        /* set global varialbes */
        curproc = pIdleProc;
        KASSERT(NULL != curproc); /* KASSERT required in grading guidline */
        KASSERT(PID_IDLE == curproc->p_pid); /* KASSERT required in grading guidline */
        curthr = ktIdleProc;
        KASSERT(NULL != curthr); /* KASSERT required in grading guidline */
        
        /* run idleProc */
        context_make_active( &(ktIdleProc->kt_ctx) );
  	/*----Yuanliang Yu code above ----*/

        panic("weenix returned to bootstrap()!!! BAD!!!\n");
        return NULL;
}

/**
 * Once we're inside of idleproc_run(), we are executing in the context of the
 * first process-- a real context, so we can finally begin running
 * meaningful code.
 *
 * This is the body of process 0. It should initialize all that we didn't
 * already initialize in kmain(), launch the init process (initproc_run),
 * wait for the init process to exit, then halt the machine.
 *
 * @param arg1 the first argument (unused)
 * @param arg2 the second argument (unused)
 */
static void *
idleproc_run(int arg1, void *arg2)
{
        int status;
        pid_t child;
        
        int retval;

        /* create init proc */
        kthread_t *initthr = initproc_create();

        init_call_all();
        GDB_CALL_HOOK(initialized);

        /* Create other kernel threads (in order) */

#ifdef __VFS__
        /* Once you have VFS remember to set the current working directory
         * of the idle and init processes */
        curproc->p_cwd = vfs_root_vn;
        (initthr->kt_proc)->p_cwd = vfs_root_vn;
	vref(vfs_root_vn);
        
        /* Here you need to make the null, zero, and tty devices using mknod */
        /* You can't do this until you have VFS, check the include/drivers/dev.h
         * file for macros with the device ID's you will need to pass to mknod */
	retval = do_mkdir("/dev");
         if( retval != 0) {
            dbgq(DBG_CORE, "MY_VFS_TEST: mkdir /den failed: %d\n", retval);
            panic("mkdir /dev failed!!!");
        }
         retval = do_mknod("/dev/null", S_IFCHR, MEM_NULL_DEVID);
         if( retval != 0) {
            dbgq(DBG_CORE, "MY_VFS_TEST: mknod null failed: %d\n", retval);
            panic("mknod null failed!!!");
        }
         
         retval = do_mknod("/dev/zero", S_IFCHR, MEM_ZERO_DEVID);
         if( retval != 0) {
            dbgq(DBG_CORE, "MY_VFS_TEST: mknod zero failed: %d\n", retval);
            panic("mknod zero failed!!!");
        }   
         
         retval = do_mknod("/dev/tty0", S_IFCHR, MKDEVID(2, 0));
         if( retval != 0) {
            dbgq(DBG_CORE, "MY_VFS_TEST: mknod tty0 failed: %d\n", retval);
            panic("mknod tty0 failed!!!");
        } 
#endif

        /* Finally, enable interrupts (we want to make sure interrupts
         * are enabled AFTER all drivers are initialized) */
        intr_enable();

        /* Run initproc */
        sched_make_runnable(initthr);

        /* Now wait for it if init proc is terminated, then poweroff */
        child = do_waitpid(-1, 0, &status);
        KASSERT(PID_INIT == child);

#ifdef __MTP__
        kthread_reapd_shutdown();
#endif


#ifdef __VFS__
        /* Shutdown the vfs: */
        dbg_print("weenix: vfs shutdown...\n");
        if (vfs_shutdown())
                panic("vfs shutdown FAILED!!\n");

#endif

        /* Shutdown the pframe system */
#ifdef __S5FS__
        pframe_shutdown();
#endif

        dbg_print("\nweenix: halted cleanly!\n");
        GDB_CALL_HOOK(shutdown);
        hard_shutdown();
        return NULL;
}

/**
 * This function, called by the idle process (within 'idleproc_run'), creates the
 * process commonly refered to as the "init" process, which should have PID 1.
 *
 * The init process should contain a thread which begins execution in
 * initproc_run().
 *
 * @return a pointer to a newly created thread which will execute
 * initproc_run when it begins executing
 */
static kthread_t *
initproc_create(void)
{
/*----Yuanliang Yu code below ----*/
        /* local varialbes */
	proc_t *pInitProc;
	kthread_t *ktInitProc;
	   
	/* create init proc and its 1 st thread */
	pInitProc = proc_create("initProc");
	KASSERT(NULL != pInitProc ); /* KASSERT required in grading guidline */
	KASSERT(PID_INIT == pInitProc->p_pid ); /* KASSERT required in grading guidline */
	
	ktInitProc = kthread_create( pInitProc, initproc_run, NULL, NULL );
	KASSERT( ktInitProc != NULL ); /* KASSERT required in grading guidline */
	
	return ktInitProc;
/*----Yuanliang Yu code above ----*/
}

/*-------------------------test code for vm--------------------------------- */

void *test_run_segfault(int arg1, void *arg2) {
	char *newargs = {NULL};
	char *newenv = {NULL};
	kernel_execve("/usr/bin/segfault", &newargs, &newenv);	
	do_exit(0);
	return 0;
}

void *test_run_hello(int arg1, void *arg2) {
	dbgq(DBG_THR, "test_run_hello enter\n");
	char *newargs = {NULL};
	char *newenv = {NULL};
	kernel_execve("/usr/bin/hello", &newargs, &newenv);	
	do_exit(0);
	return 0;
}

void *test_run_args(int arg1, void *arg2) {
	char *newargs = {NULL};
	char *newenv = {"ab cde fghi j"};
	kernel_execve("/usr/bin/args", &newargs, &newenv);	
	do_exit(0);
	return 0;
}

void *test_run_kshell(int arg1, void *arg2) {
	char *newargs = {NULL};
	char *newenv = {NULL};
	kernel_execve("/usr/bin/kshell", &newargs, &newenv);	
	do_exit(0);
	return 0;
}

void *test_run_spin(int arg1, void *arg2) {
	char *newargs = {NULL};
	char *newenv = {NULL};
	kernel_execve("/usr/bin/spin", &newargs, &newenv);	
	do_exit(0);
	return 0;
}
/*-------------------------------------------------------------------------*/





/**
 * The init thread's function changes depending on how far along your Weenix is
 * developed. Before VM/FI, you'll probably just want to have this run whatever
 * tests you've written (possibly in a new process). After VM/FI, you'll just
 * exec "/bin/init".
 *
 * Both arguments are unused.
 *
 * @param arg1 the first argument (unused)
 * @param arg2 the second argument (unused)
 */
static void *
initproc_run(int arg1, void *arg2)
{
	/*----------------------------------VM TEST-----------------------------------------*/
	int status = 0;
#ifdef TEST_SEGFAULT
	proc_t *test_proc_segfault = proc_create("test_proc_segfault");
	kthread_t *test_thread_segfault = kthread_create(test_proc_segfault, test_run_segfault, NULL, NULL);
	sched_make_runnable(test_thread_segfault);
	do_waitpid(-1, 0, &status);
	dbgq(DBG_TEST, "initproc_run(): The test process usr/bin/segfault return, the status is %d\n", status);
#endif

#ifdef TEST_HELLO
	proc_t *test_proc_hello = proc_create("test_proc_hello");
	kthread_t *test_thread_hello = kthread_create(test_proc_hello, test_run_hello, NULL, NULL);
	sched_make_runnable(test_thread_hello);
	do_waitpid(-1, 0, &status);
	dbgq(DBG_TEST, "initproc_run(): The test process usr/bin/hello return, the status is %d\n", status);
#endif
	
#ifdef TEST_ARGS
	proc_t *test_proc_args = proc_create("test_proc_args");
	kthread_t *test_thread_args = kthread_create(test_proc_args, test_run_args, NULL, NULL);
	sched_make_runnable(test_thread_args);
	do_waitpid(-1, 0, &status);
	dbgq(DBG_TEST, "initproc_run(): The test process usr/bin/args return, the status is %d\n", status);
#endif

#ifdef TEST_SPIN
	proc_t *test_proc_spin = proc_create("test_proc_spin");
	kthread_t *test_thread_spin = kthread_create(test_proc_spin, test_run_spin, NULL, NULL);
	sched_make_runnable(test_thread_spin);
	do_waitpid(-1, 0, &status);
	dbgq(DBG_TEST, "initproc_run(): The test process usr/bin/spin return, the status is %d\n", status);
#endif

#ifdef TEST_KSHELL
	proc_t *test_proc_kshell = proc_create("test_proc_kshell");
	kthread_t *test_thread_kshell = kthread_create(test_proc_kshell, test_run_kshell, NULL, NULL);
	sched_make_runnable(test_thread_kshell);
	do_waitpid(-1, 0, &status);
	dbgq(DBG_TEST, "initproc_run(): The test process usr/bin/kshell return, the status is %d\n", status);
#endif


	do_exit(0);

	return NULL;
/*----Yuanliang Yu code above : for Kernel Assignment 2 test purpos ----*/
}

/*
 * Clears all interrupts and halts, meaning that we will never run
 * again.
 */
static void
hard_shutdown()
{
#ifdef __DRIVERS__
        vt_print_shutdown();
#endif
        __asm__ volatile("cli; hlt");
}
